/** \file
 * \brief Windows Base Procedure
 *
 * See Copyright Notice in "iup.h"
 */

#include <stdio.h>              
#include <stdlib.h>
#include <string.h>             

#include <windows.h>
#include <commctrl.h>

#include "iup.h"
#include "iupcbs.h"

#include "iup_object.h"
#include "iup_childtree.h"
#include "iup_key.h"
#include "iup_str.h"
#include "iup_class.h"
#include "iup_attrib.h"
#include "iup_focus.h"
#include "iup_image.h"
#include "iup_dialog.h"
#include "iup_drvinfo.h"
#include "iup_drv.h"

#include "iupwin_drv.h"
#include "iupwin_handle.h"
#include "iupwin_brush.h"


#ifndef  XBUTTON1
#define XBUTTON1      0x0001     /* not defined in MingW3 */
#endif

int iupwinClassExist(const char* name)
{
  WNDCLASS WndClass;
  if (GetClassInfo(iupwin_hinstance, name, &WndClass))
    return 1;
  return 0;
}

int iupwinGetScreenRes(void)
{
  int res;
  HDC ScreenDC = GetDC(NULL);
  res = GetDeviceCaps(ScreenDC, LOGPIXELSY);
  ReleaseDC(NULL, ScreenDC);
  return res;
}

void iupdrvActivate(Ihandle* ih)
{
  /* do not use BM_CLICK because it changes the focus 
     and does not animates the button press */
  SendMessage(ih->handle, BM_SETSTATE, TRUE, 0);
  IupFlush();
  Sleep(150);
  SendMessage(GetParent(ih->handle), WM_COMMAND, MAKEWPARAM(0, BN_CLICKED), (LPARAM)ih->handle);
  SendMessage(ih->handle, BM_SETSTATE, FALSE, 0);
}

WCHAR* iupwinStrChar2Wide(const char* str)
{
  if (str)
  {
    int len = strlen(str)+1;
    WCHAR* wstr = malloc(len * sizeof(WCHAR));
    MultiByteToWideChar(CP_ACP, 0, str, -1, wstr, len);
    return wstr;
  }

  return NULL;
}

int iupdrvGetScrollbarSize(void)
{
  int xv = GetSystemMetrics(SM_CXVSCROLL);
  int yh = GetSystemMetrics(SM_CYHSCROLL);
  return xv > yh ? xv : yh;
}

void iupdrvReparent(Ihandle* ih)
{
  SetParent(ih->handle, iupChildTreeGetNativeParentHandle(ih));
}

void iupdrvBaseLayoutUpdateMethod(Ihandle *ih)
{
  SetWindowPos(ih->handle, NULL, ih->x, ih->y, ih->currentwidth, ih->currentheight,
               SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOOWNERZORDER);
}

void iupdrvRedrawNow(Ihandle *ih)
{
  /* REDRAW Now */
  RedrawWindow(ih->handle,NULL,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_INTERNALPAINT|RDW_UPDATENOW);
}

void iupdrvPostRedraw(Ihandle *ih)
{
  /* Post a REDRAW */
  /* can NOT use RDW_NOCHILDREN because IupList has internal children that needs to be redraw */
  RedrawWindow(ih->handle,NULL,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_INTERNALPAINT);  
}

void iupdrvScreenToClient(Ihandle* ih, int *x, int *y)
{
  POINT p;
  p.x = *x;
  p.y = *y;
  ScreenToClient(ih->handle, &p);
  *x = p.x;
  *y = p.y;
}

static void winTrackMouse(HWND hwnd, int enter)
{
  TRACKMOUSEEVENT mouse;
  mouse.cbSize = sizeof(TRACKMOUSEEVENT);

  if (enter)
    mouse.dwFlags = TME_HOVER;
  else
    mouse.dwFlags = TME_LEAVE;

  mouse.hwndTrack = hwnd;
  mouse.dwHoverTime = 1;
  TrackMouseEvent(&mouse);
}

static void winCallEnterLeaveWindow(Ihandle *ih, int enter)
{
  Icallback cb = NULL;

  if (!ih->iclass->is_interactive)
    return;

  if (enter)
  {
    winTrackMouse(ih->handle, 0);

    if (!iupAttribGetInt(ih, "_IUPWIN_ENTERWIN"))
    {
      cb = IupGetCallback(ih,"ENTERWINDOW_CB");
      iupAttribSetStr(ih, "_IUPWIN_ENTERWIN", "1");
    }
  }
  else 
  {
    cb = IupGetCallback(ih,"LEAVEWINDOW_CB");
    iupAttribSetStr(ih, "_IUPWIN_ENTERWIN", NULL);
  }

  if (cb)
    cb(ih);
}

void iupwinMergeStyle(Ihandle* ih, DWORD old_mask, DWORD value)
{
  DWORD dwStyle = GetWindowLong(ih->handle, GWL_STYLE);
  dwStyle &= ~(old_mask); /* clear old bits */
  dwStyle |= value;
  SetWindowLong(ih->handle, GWL_STYLE, dwStyle);
}

void iupwinSetStyle(Ihandle* ih, DWORD value, int set)
{
  DWORD dwStyle = GetWindowLong(ih->handle, GWL_STYLE);
  if (set)
    dwStyle |= value;
  else
    dwStyle &= ~(value);
  SetWindowLong(ih->handle, GWL_STYLE, dwStyle);
}

int iupwinBaseProc(Ihandle* ih, UINT msg, WPARAM wp, LPARAM lp, LRESULT *result)
{ 
  (void)lp;

  switch (msg)
  {
  case WM_GETDLGCODE:
    {
      *result = DLGC_WANTALLKEYS;
      return 1;
    }
  case WM_NOTIFY: /* usually sent only to parent, 
                     but TIPs are configured to be handled here */
    {
      NMHDR* msg_info = (NMHDR*)lp;
      if (msg_info->code == TTN_GETDISPINFO)
        iupwinTipsGetDispInfo(lp);
      break;
    }
  case WM_DROPFILES:
    iupwinDropFiles((HDROP)wp, ih);
    break;
  case WM_HELP:
    {
      Ihandle* child;
      HELPINFO* help_info = (HELPINFO*)lp;

      if (help_info->iContextType == HELPINFO_MENUITEM)
        child = iupwinMenuGetItemHandle((HMENU)help_info->hItemHandle, (int)help_info->iCtrlId);
      else
        child = iupwinHandleGet(help_info->hItemHandle);

      if (child)
      {
        Icallback cb = (Icallback) IupGetCallback(child, "HELP_CB");
        if (cb) 
        {
          if (cb(child) == IUP_CLOSE) 
            IupExitLoop();

          *result = 0;
          return 1;  /* abort default processing */
        }
      }
      break;
    }
  case WM_MOUSELEAVE:
    winCallEnterLeaveWindow(ih, 0);
    break;
  case WM_MOUSEMOVE:
    winCallEnterLeaveWindow(ih, 1);
    break;
  case WM_KEYDOWN:
  case WM_SYSKEYDOWN:
    if (!iupwinKeyEvent(ih, (int)wp, 1))
    {
      *result = 0;
      return 1;   /* abort default processing */
    }
    break;
  case WM_SYSKEYUP:
  case WM_KEYUP:
    {
      int ret;
      if (wp == VK_SNAPSHOT) /* called only on key up */
      {
        ret = iupwinKeyEvent(ih, (int)wp, 1);
        if (ret && iupObjectCheck(ih))
          ret = iupwinKeyEvent(ih, (int)wp, 0);
      }
      else
        ret = iupwinKeyEvent(ih, (int)wp, 0);

      if (!ret)
      {
        *result = 0;
        return 1;  /* abort default processing */
      }

      break;
    }
  case WM_SETFOCUS:
    iupwinWmSetFocus(ih);
    break;
  case WM_KILLFOCUS:
    iupCallKillFocusCb(ih);
    break;
  case WOM_CLOSE:
  case WOM_DONE:
  case WOM_OPEN:
    {
      IFni cb = (IFni)IupGetCallback(ih, "WOM_CB");
      if (cb)
      {
        int v = -2; /* Error */
        switch(msg)
        {
        case WOM_OPEN:  v = 1;  break;
        case WOM_DONE:  v = 0;  break;
        case WOM_CLOSE: v = -1; break;
        }
        cb(ih, v);
      }
      break;
    }
  }

  return 0;
}

LRESULT CALLBACK iupwinBaseWinProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{   
  int ret = 0;
  LRESULT result = 0;
  IwinProc winProc;
  WNDPROC oldProc;
  Ihandle *ih;

  ih = iupwinHandleGet(hwnd); 
  if (!ih)
    return DefWindowProc(hwnd, msg, wp, lp);  /* should never happen */

  /* retrieve the control previous procedure for subclassing */
  oldProc = (WNDPROC)IupGetCallback(ih, "_IUPWIN_OLDPROC_CB");

  /* check if the element defines a custom procedure */
  winProc = (IwinProc)IupGetCallback(ih, "_IUPWIN_CTRLPROC_CB");
  if (winProc)
    ret = winProc(ih, msg, wp, lp, &result);
  else
    ret = iupwinBaseProc(ih, msg, wp, lp, &result);

  if (ret)
    return result;
  else
    return CallWindowProc(oldProc, hwnd, msg, wp, lp);
}

static Ihandle* winContainerWmCommandGetIhandle(Ihandle *ih, WPARAM wp, LPARAM lp)
{
  /* WPARAM - if HIWORD is 0 if the message is from a menu.
              or HIWORD is 1 if the message is from an accelerator.
              or HIWORD is the notification code if the message is from a control.
              LOWORD is the identifier.
     LPARAM - the control sending the message or 0. */

  Ihandle *child = NULL;

  if (HIWORD(wp)==0 && lp==0 && LOWORD(wp)>10)
  {
    Ihandle* dlg_menu = IupGetAttributeHandle(ih, "MENU");
    if (dlg_menu)
      child = iupwinMenuGetItemHandle((HMENU)dlg_menu->handle, LOWORD(wp));  /* menu */
  }
  else 
  {
    if (lp==0)
      child = ih;                              /* native parent */
    else
    {
      child = iupwinHandleGet((void*)lp);       /* control */
      if (!child)
        child = iupwinHandleGet((void*)GetParent((HWND)lp));       /* control */
    }
  }

  return child;
}

int iupwinBaseContainerProc(Ihandle* ih, UINT msg, WPARAM wp, LPARAM lp, LRESULT *result)
{
  /* All messages here are sent to the parent Window, 
     but they are usefull for child controls.  */

  switch (msg)
  {
  case WM_COMMAND:
    {
      Ihandle* child = winContainerWmCommandGetIhandle(ih, wp, lp);
      if (child)
      {
        IFnii cb = (IFnii)IupGetCallback(child, "_IUPWIN_COMMAND_CB");
        if (cb)
          cb(child, wp, lp);
      }

      break;
    }
  case WM_CTLCOLOREDIT:
  case WM_CTLCOLORLISTBOX:
  case WM_CTLCOLORBTN:
  case WM_CTLCOLORSCROLLBAR:
  case WM_CTLCOLORSTATIC:
    {
      Ihandle* child = iupwinHandleGet((void*)lp);
      if (child && iupChildTreeGetNativeParent(child)==ih)
      {
        IFctlColor cb = (IFctlColor)IupGetCallback(child, "_IUPWIN_CTLCOLOR_CB");
        if (cb)
          return cb(child, (HDC)wp, result);
      }
      break;
    }
  case WM_DRAWITEM:   /* for OWNERDRAW controls */
    {
      Ihandle *child = NULL;
      DRAWITEMSTRUCT *drawitem = (LPDRAWITEMSTRUCT)lp;
      if (!drawitem)
        break;

      if (wp == 0) /* a menu */
        child = iupwinMenuGetItemHandle((HMENU)drawitem->hwndItem, drawitem->itemID);
      else
      {
        child = iupwinHandleGet(drawitem->hwndItem); 
        if (child && iupChildTreeGetNativeParent(child)!=ih)
          child = NULL;
      }

      if (child)
      {
        IFdrawItem cb = (IFdrawItem)IupGetCallback(child, "_IUPWIN_DRAWITEM_CB");
        if (cb) 
        {
          cb(child, (void*)drawitem);
          *result = TRUE;
          return 1;
        }
      }
      break;
    }
  case WM_HSCROLL:
  case WM_VSCROLL:
    {
      Ihandle *child = iupwinHandleGet((void*)lp);
      if (child && iupChildTreeGetNativeParent(child)==ih)
      {
        IFni cb = (IFni)IupGetCallback(child, "_IUPWIN_CUSTOMSCROLL_CB");
        if (cb)
          cb(child, LOWORD(wp));
      }
      break;
    }
  case WM_NOTIFY:   /* Currently, the following controls support custom draw functionality: 
                       Header, List-view, Rebar, Toolbar, ToolTip, Trackbar, Tree-view.
                       And for Button if using Windows XP Style. */
    {
      Ihandle *child;
      NMHDR* msg_info = (NMHDR*)lp;
      if (!msg_info)
        break;

      child = iupwinHandleGet(msg_info->hwndFrom); 
      if (child && iupChildTreeGetNativeParent(child)==ih)
      {
        IFnotify cb = (IFnotify)IupGetCallback(child, "_IUPWIN_NOTIFY_CB");
        if (cb)
        {
          if (cb(child, (void*)msg_info, result))
            return 1;
        }
      }
      break;
    }
  }

  return iupwinBaseProc(ih, msg, wp, lp, result);
}

void iupwinChangeProc(Ihandle *ih, WNDPROC new_proc)
{
  IupSetCallback(ih, "_IUPWIN_OLDPROC_CB", (Icallback)GetWindowLongPtr(ih->handle, GWLP_WNDPROC));
  SetWindowLongPtr(ih->handle, GWLP_WNDPROC, (LONG_PTR)new_proc);
}

void iupdrvBaseUnMapMethod(Ihandle* ih)
{
  WNDPROC oldProc = (WNDPROC)IupGetCallback(ih, "_IUPWIN_OLDPROC_CB");
  if (oldProc)
  {
    SetWindowLongPtr(ih->handle, GWLP_WNDPROC, (LONG_PTR)oldProc);
    IupSetCallback(ih, "_IUPWIN_OLDPROC_CB",  NULL);
  }

  /* remove the association before destroying */
  iupwinHandleRemove(ih);

  /* destroys window (it will remove from parent) */
  DestroyWindow(ih->handle);
}

void iupwinDropFiles(HDROP hDrop, Ihandle *ih)
{
  char *filename;
  int i, numFiles, numchar, ret;
  POINT point;

  IFnsiii cb = (IFnsiii)IupGetCallback(ih, "DROPFILES_CB");
  if (!cb) return; 

  numFiles = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);
  DragQueryPoint(hDrop, &point);  
  for (i = 0; i < numFiles; i++)
  {
    numchar = DragQueryFile(hDrop, i, NULL, 0);
    filename = malloc(numchar+1); 
    if (!filename)
      break;

    DragQueryFile(hDrop, i, filename, numchar+1);

    ret = cb(ih, filename, numFiles-i-1, (int) point.x, (int) point.y); 

    free(filename);

    if (ret == IUP_IGNORE)
      break;
  }
  DragFinish(hDrop);
}

int iupwinGetColorRef(Ihandle *ih, char *name, COLORREF *color)
{
  unsigned char r, g, b;
  /* must use IupGetAttribute to use inheritance */
  if (iupStrToRGB(IupGetAttribute(ih, name), &r, &g, &b))
  {
    *color = RGB(r,g,b);
    return 1;
  }
  return 0;
}

int iupwinGetParentBgColor(Ihandle* ih, COLORREF* cr)
{
  unsigned char r, g, b;
  char* color = iupBaseNativeParentGetBgColorAttrib(ih);
  if (iupStrToRGB(color, &r, &g, &b))
  {
    *cr = RGB(r,g,b);
    return 1;
  }
  return 0;
}

int iupdrvBaseSetZorderAttrib(Ihandle* ih, const char* value)
{
  if (IsWindowVisible(ih->handle))
  {
    if (iupStrEqualNoCase(value, "TOP"))
      SetWindowPos(ih->handle, HWND_TOP, 0, 0, 0, 0, 
                  SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
    else
      SetWindowPos(ih->handle, HWND_BOTTOM, 0, 0, 0, 0, 
                  SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOACTIVATE);
  }

  return 0;
}

void iupdrvSetVisible(Ihandle* ih, int visible)
{
  if (iupStrEqual(ih->iclass->name, "colorbar"))
    ih=ih;
  ShowWindow(ih->handle, visible? SW_SHOWNORMAL: SW_HIDE);
}

int iupdrvIsVisible(Ihandle* ih)
{
  return IsWindowVisible(ih->handle);
}

int iupdrvIsActive(Ihandle* ih)
{
  return IsWindowEnabled(ih->handle);
}

void iupdrvSetActive(Ihandle* ih, int enable)
{
  EnableWindow(ih->handle, enable);
}

int iupdrvBaseSetTitleAttrib(Ihandle* ih, const char* value)
{
  if (!value) value = "";
  SetWindowText(ih->handle, value);
  return 0;
}

char* iupdrvBaseGetTitleAttrib(Ihandle* ih)
{
  int nc = GetWindowTextLength(ih->handle);
  if (nc)
  {
    char* str = iupStrGetMemory(nc+1);
    GetWindowText(ih->handle, str, nc+1);
    return str;
  }
  else
    return NULL;
}

int iupwinSetDragDropAttrib(Ihandle* ih, const char* value)
{
  if (iupStrBoolean(value))
    DragAcceptFiles(ih->handle, TRUE);
  else
    DragAcceptFiles(ih->handle, FALSE);
  return 1;
}

char *iupdrvBaseGetXAttrib(Ihandle *ih)
{
  char* str = iupStrGetMemory(20);
  RECT rect;
  GetWindowRect(ih->handle, &rect);
  sprintf(str, "%d", (int)rect.left);
  return str;
}

char *iupdrvBaseGetYAttrib(Ihandle *ih)
{
  char* str = iupStrGetMemory(20);
  RECT rect;
  GetWindowRect(ih->handle, &rect);
  sprintf(str, "%d", (int)rect.top);
  return str;
}

char* iupdrvBaseGetClientSizeAttrib(Ihandle* ih)
{
  char* str = iupStrGetMemory(20);
  RECT rect;
  GetClientRect(ih->handle, &rect);
  sprintf(str, "%dx%d", (int)(rect.right-rect.left), (int)(rect.bottom-rect.top));
  return str;
}

#ifndef IDC_HAND
#define IDC_HAND            MAKEINTRESOURCE(32649)
#endif
#ifndef IDC_APPSTARTING
#define IDC_APPSTARTING     MAKEINTRESOURCE(32650) 
#endif
#ifndef IDC_HELP
#define IDC_HELP            MAKEINTRESOURCE(32651)
#endif

static HCURSOR winLoadComCtlCursor(LPCTSTR lpCursorName)
{
  HCURSOR cur = NULL;
  HINSTANCE hinstDll = LoadLibrary("comctl32.dll");
  if (hinstDll)
  {
    cur = LoadCursor(hinstDll, lpCursorName);
    FreeLibrary(hinstDll);
  }
  return cur;
}

static HCURSOR winGetCursor(Ihandle* ih, const char* name)
{
  static struct {
    const char* iupname;
    const char* sysname;
  } table[] = {
    {"NONE",      NULL}, 
    {"NULL",      NULL}, 
    {"ARROW",     IDC_ARROW},
    {"BUSY",      IDC_WAIT},
    {"CROSS",     IDC_CROSS},
    {"HAND",      IDC_HAND},
    {"MOVE",      IDC_SIZEALL},
    {"RESIZE_N",  IDC_SIZENS},
    {"RESIZE_S",  IDC_SIZENS},
    {"RESIZE_NS", IDC_SIZENS},
    {"RESIZE_W",  IDC_SIZEWE},
    {"RESIZE_E",  IDC_SIZEWE},
    {"RESIZE_WE", IDC_SIZEWE},
    {"RESIZE_NE", IDC_SIZENESW},
    {"RESIZE_SE", IDC_SIZENWSE},
    {"RESIZE_NW", IDC_SIZENWSE},
    {"RESIZE_SW", IDC_SIZENESW},
    {"TEXT",      IDC_IBEAM},
    {"HELP",      IDC_HELP},
    {"IUP",       IDC_HELP},
    {"NO",        IDC_NO},
    {"UPARROW",   IDC_UPARROW},
    {"APPSTARTING", IDC_APPSTARTING}
  };

  HCURSOR cur = NULL;
  char str[50];
  int i, count = sizeof(table)/sizeof(table[0]);

  /* check the cursor cache first (per control)*/
  sprintf(str, "_IUPWIN_CURSOR_%s", name);
  cur = (HCURSOR)iupAttribGet(ih, str);
  if (cur)
    return cur;

  /* check the pre-defined IUP names first */
  for (i = 0; i < count; i++)
  {
    if (iupStrEqualNoCase(name, table[i].iupname)) 
    {
      if (table[i].sysname)
        cur = LoadCursor(NULL, table[i].sysname);
      else
        cur = NULL;

      break;
    }
  }

  if (i == count)
  {
    /* check other system cursors */

    if (iupStrEqualNoCase(name, "PEN"))
      name = "CURSOR_PEN"; /* name in "iup.rc" */

    /* check for an name defined cursor */
    cur = iupImageGetCursor(name);
  }

  if (!cur)
  {
    if (iupStrEqualNoCase(name, "SPLITTER_VERT"))
      cur = winLoadComCtlCursor(MAKEINTRESOURCE(107));
    else if (iupStrEqualNoCase(name, "SPLITTER_HORIZ"))
      cur = winLoadComCtlCursor(MAKEINTRESOURCE(135));
  }

  iupAttribSetStr(ih, str, (char*)cur);
  return cur;
}

int iupdrvBaseSetCursorAttrib(Ihandle* ih, const char* value)
{
  /* Cursor can be NULL in Windows. */
  HCURSOR hCur = winGetCursor(ih, value);
  iupAttribSetStr(ih, "_IUPWIN_HCURSOR", (char*)hCur);  /* To be used in WM_SETCURSOR */
  /* refresh the cursor */
  SendMessage(ih->handle, WM_SETCURSOR, (WPARAM)ih->handle, MAKELPARAM(1,WM_MOUSEMOVE));
  return 1;
}

void iupdrvBaseRegisterCommonAttrib(Iclass* ic)
{
  iupClassRegisterAttribute(ic, "HFONT", iupwinGetHFontAttrib, NULL, NULL, NULL, IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT|IUPAF_NO_STRING);
}

int iupwinButtonDown(Ihandle* ih, UINT msg, WPARAM wp, LPARAM lp)
{
  char status[IUPKEY_STATUS_SIZE] = IUPKEY_STATUS_INIT;
  int ret, doubleclick = 0;
  int b = 0;

  IFniiiis cb = (IFniiiis) IupGetCallback(ih, "BUTTON_CB");
  if (!cb)
    return 0;

  if (msg==WM_XBUTTONDBLCLK ||
      msg==WM_LBUTTONDBLCLK ||
      msg==WM_MBUTTONDBLCLK ||
      msg==WM_RBUTTONDBLCLK)
    doubleclick = 1;

  iupwinButtonKeySetStatus(LOWORD(wp), status, doubleclick);

  if (msg==WM_LBUTTONDOWN || msg==WM_LBUTTONDBLCLK)
    b = IUP_BUTTON1;
  else if (msg==WM_MBUTTONDOWN || msg==WM_MBUTTONDBLCLK)
    b = IUP_BUTTON2;
  else if (msg==WM_RBUTTONDOWN || msg==WM_RBUTTONDBLCLK)
    b = IUP_BUTTON3;
  else if (msg==WM_XBUTTONDOWN || msg==WM_XBUTTONDBLCLK)
  {
    if (HIWORD(wp) == XBUTTON1)
      b = IUP_BUTTON4;
    else
      b = IUP_BUTTON5;
  }

  ret = cb(ih, b, 1, (int)(short)LOWORD(lp), (int)(short)HIWORD(lp), status);
  if (ret == IUP_CLOSE)
    IupExitLoop();
  else if (ret == IUP_IGNORE)
    return -1;

  return 1;
}

int iupwinButtonUp(Ihandle* ih, UINT msg, WPARAM wp, LPARAM lp)
{
  char status[IUPKEY_STATUS_SIZE] = IUPKEY_STATUS_INIT;
  int ret, b=0;
  IFniiiis cb = (IFniiiis) IupGetCallback(ih, "BUTTON_CB");
  if (!cb)
    return 0;

  iupwinButtonKeySetStatus(LOWORD(wp), status, 0);

  /* also updates the button status, since wp could not have the flag */
  if (msg==WM_LBUTTONUP)
  {
    b = IUP_BUTTON1;
    iupKEY_SETBUTTON1(status);  
  }
  else if (msg==WM_MBUTTONUP)
  {
    b = IUP_BUTTON2;
    iupKEY_SETBUTTON2(status);
  }
  else if (msg==WM_RBUTTONUP)
  {
    b = IUP_BUTTON3;
    iupKEY_SETBUTTON3(status);
  }
  else if (msg==WM_XBUTTONUP)
  {
    if (HIWORD(wp) == XBUTTON1)
    {
      b = IUP_BUTTON4;
      iupKEY_SETBUTTON4(status);
    }
    else
    {
      b = IUP_BUTTON5;
      iupKEY_SETBUTTON5(status);
    }
  }

  ret = cb(ih, b, 0, (int)(short)LOWORD(lp), (int)(short)HIWORD(lp), status);
  if (ret == IUP_CLOSE)
    IupExitLoop();
  else if (ret == IUP_IGNORE)
    return -1;

  return 1;
}

int iupwinMouseMove(Ihandle* ih, UINT msg, WPARAM wp, LPARAM lp)
{
  IFniis cb = (IFniis)IupGetCallback(ih, "MOTION_CB");
  if (cb)
  {
    char status[IUPKEY_STATUS_SIZE] = IUPKEY_STATUS_INIT;
    iupwinButtonKeySetStatus(LOWORD(wp), status, 0);
    cb(ih, (int)(short)LOWORD(lp), (int)(short)HIWORD(lp), status);
    return 1;
  }
  (void)msg;
  return 0;
}

void iupwinGetNativeParentStyle(Ihandle* ih, DWORD *dwExStyle, DWORD *dwStyle)
{
  *dwStyle |= WS_CLIPCHILDREN;

  if (iupAttribGetBoolean(IupGetDialog(ih), "COMPOSITED"))
    *dwExStyle |= WS_EX_COMPOSITED;
}

int iupwinCreateWindowEx(Ihandle* ih, LPCSTR lpClassName, DWORD dwExStyle, DWORD dwStyle)
{
  ih->serial = iupDialogGetChildId(ih);

  ih->handle = CreateWindowEx(dwExStyle,  /* extended window style */
    lpClassName,                  /* window class */
    NULL,                         /* title */
    dwStyle,                      /* window style */
    0,                            /* x-position */
    0,                            /* y-position */
    10,                           /* default width to avoid 0 */
    10,                           /* default height to avoid 0 */
    iupChildTreeGetNativeParentHandle(ih),     /* window parent */
    (HMENU)ih->serial,            /* child identifier */
    iupwin_hinstance,             /* instance of app. */
    NULL);

  if (!ih->handle)
    return 0;

  /* associate HWND with Ihandle*, all Win32 controls must call this. */
  iupwinHandleSet(ih);

  /* replace the WinProc to handle base callbacks */
  iupwinChangeProc(ih, iupwinBaseWinProc);

  return 1;
}

char* iupwinGetClipboardText(Ihandle* ih)
{
  HANDLE Handle;
  char* str;

  if (!IsClipboardFormatAvailable(CF_TEXT))
    return NULL;

  if (!OpenClipboard(ih->handle))
    return NULL;

  Handle = GetClipboardData(CF_TEXT);
  if (!Handle)
  {
    CloseClipboard();
    return NULL;
  }
  
  str = (char*)GlobalLock(Handle);
  str = iupStrDup(str);
  
  GlobalUnlock(Handle);
  
  CloseClipboard();

  return str;
}
